<%= render "docs/_header.html", conn: @conn, page: @page %>

<%= doc_prose do %>
    <a href={Routes.website_path(@conn, :aml)}><img src={Routes.static_path(@conn, "/images/logos/aml.png")} alt="AML logo" class="mx-auto max-w-sm"></a>
    <p class="text-center">
        <a href={Routes.website_path(@conn, :aml)}>Home page</a> •
        <a href={Routes.website_path(@conn, :converter, "aml")}>Converter</a> •
        <a href={Azimutt.config(:npm_aml_url)} target="_blank" rel="noopener noreferrer">npm package</a>
    </p>

    <p class="lead">
        <strong>AML</strong> (Azimutt Markup Language) is the <strong>easiest language to design databases</strong>.<br>
        Made to be fast to learn and write.
    </p>

    <%= render "docs/_h2.html", title: "Why AML?" %>
    <ul>
        <li><strong>Structured text</strong> is WAY better than GUI: portable, copy/paste, find/replace, versioning...</li>
        <li>It's <strong>simpler, faster to write and less error-prone than SQL</strong> or other database schema DSLs</li>
        <li><strong>Made for humans</strong>: readable, flexible, can hold <a href={Routes.website_path(@conn, :doc, ["aml", "properties"])}>custom properties</a></li>
        <li><strong>Database agnostic</strong>: hold concepts, not specific syntax, can be <a href={Routes.website_path(@conn, :converter, "aml")}>converted to other dialects</a></li>
        <li><strong>Free</strong> as 🕊️ but also 🍺</li>
    </ul>
    <p>In short, it's perfect for fast prototyping and brainstorming. And it even has its own <a href={Azimutt.config(:vscode_extension_url)} target="_blank" rel="noopener">VS Code extension</a>!</p>

    <%= render "docs/_h2.html", title: "Example" %>
    <pre><code class="hljs aml">users
  id uuid pk
  name varchar
  email varchar index
  role user_role(admin, guest)=guest

posts
  id uuid pk
  title varchar
  content text | formatted in markdown
  created_at timestamp=`now()`
  created_by uuid -> users(id) # inline relation
</code></pre>

    <%= render "docs/_h2.html", title: "Introduction" %>
    <p>This page will give you a <strong>quick overview</strong> of how to use AML, follow links for an exhaustive specification.</p>
    <ul>
        <li><a href="#entities">Entities</a></li>
        <li><a href="#relations">Relations</a></li>
        <li><a href="#types">Types</a></li>
        <li><a href="#full-example">Full example</a></li>
        <li><a href="#migration-from-v1">Migration from v1</a></li>
        <li><a href="#other-database-schema-dsls">Other database schema DSLs</a></li>
    </ul>
    <p>
        One last thing, AML <a href={Routes.website_path(@conn, :doc, ["aml", "comments"])}>comments</a> are single line and start with <code>#</code>,
        you will see them in many places 😉
    </p>

    <%= render "docs/_h2.html", title: "Entities" %>
    <p>
        <a href={Routes.website_path(@conn, :doc, ["aml", "entities"])}>Entities</a> can be used to model objects from databases,
        such as <strong>tables</strong> or <strong>collections</strong>.
    </p>
    <p>Defining one in AML can't be simpler, just type its name:</p>
    <pre><code class="hljs aml">posts
</code></pre>
    <p>Entities can have attributes with several options like a type, nullability, indexes, constraints and relations.</p>
    <p>Here is how they look:</p>
    <pre><code class="hljs aml">posts
  id uuid pk
  slug varchar(256) unique
  title varchar index
  status post_status(draft, published, archived)=draft index
  content text nullable
  tags "varchar[]"
  props json
    needs_review bool
    reviewed_by -> users(id)
  created_by -> users(id)
  created_at "timestamp with time zone"=`now()`
</code></pre>
    <p>
        You can define them inside a <a href={Routes.website_path(@conn, :doc, ["aml", "namespaces"])}>namespace</a>
        and give them an <a href={"#{Routes.website_path(@conn, :doc, ["aml", "entities"])}#alias"}>alias</a> name for easier referencing:
    </p>
    <pre><code class="hljs aml">core.public.users as u
  id uuid pk
  name varchar

core.public.posts as p
  id uuid pk
  title varchar
  created_by -> u(id)
</code></pre>
    <p>
        And you can document them both with structured <a href={Routes.website_path(@conn, :doc, ["aml", "properties"])}>properties</a>
        or unstructured <a href={Routes.website_path(@conn, :doc, ["aml", "documentation"])}>documentation</a>:
    </p>
    <pre><code class="hljs aml">events {color: red, scope: tracking, tags: [pii, deprecated]} | store all user events
  id int pk {autoIncrement}
  name varchar index | should be structured with `context__object__action` format
  item_kind varchar {values: [users, posts, projects]} | polymorphic relation
  item_id uuid
</code></pre>

    <%= render "docs/_h2.html", title: "Relations" %>
    <p>
        <a href={Routes.website_path(@conn, :doc, ["aml", "relations"])}>Relations</a> can model references, like foreign keys, or source for lineage,
        depending on how you want to use them.
    </p>
    <p>
        They mostly use the <code>-></code> symbol in entity definition (like used above) but can also be defined standalone with the <code>rel</code> keyword
        and use other cardinality with <code>--</code> for <a href={"#{Routes.website_path(@conn, :doc, ["aml", "relations"])}#one-to-one"}>one-to-one</a>
        and <code>&lt;></code> for <a href={"#{Routes.website_path(@conn, :doc, ["aml", "relations"])}#many-to-many"}>many-to-many</a>.
    </p>
    <pre><code class="hljs aml">users
  id uuid pk

profiles
  id uuid pk
  user_id uuid -- users(id)

projects
  id uuid pk &lt;> users(id)
  created_by -> users(id)

events
  id uuid pk
  created_by uuid

rel events(created_by) -> users(id)
</code></pre>
    <p>
        For fastest definition, you can omit the target attribute when the target table has a primary key with a single attribute.
        As well as the attribute type, it will be inherited from the target attribute:
    </p>
    <pre><code class="hljs aml">users
  id uuid pk

events
  id uuid pk
  created_by -> users
</code></pre>
    <p>
        AML supports <a href={"#{Routes.website_path(@conn, :doc, ["aml", "relations"])}#polymorphic-relation"}>polymorphic relations</a>
        by adding the kind attribute key and value inside the relation symbol:
    </p>
    <pre><code class="hljs aml">users
  id uuid pk

projects
  id uuid pk

events
  id uuid pk
  item_kind event_items(users, projects)
  item_id
  created_by -> users

rel events(item_id) -item_kind=users> users
rel events(item_id) -item_kind=projects> projects
</code></pre>
    <p>
        It also supports <a href={"#{Routes.website_path(@conn, :doc, ["aml", "relations"])}#composite-relation"}>composite relations</a>
        by listing used attributes in the parenthesis:
    </p>
    <pre><code class="hljs aml">credentials
  provider_key varchar pk
  provider_uid varchar pk
  user_id -> users

credential_details
  provider_key varchar pk
  provider_uid varchar pk
  provider_data json

rel credential_details(provider_key, provider_uid) -> credentials(provider_key, provider_uid)
</code></pre>
    <p>Of course, relations can be used with nested attributes:</p>
    <pre><code class="hljs aml">users
  id int pk
  friends "json[]"
    id number -> users(id) # inline relation

events
  id uuid pk
  details json
    user_id number

rel events(details.user_id) -> users(id) # standalone relation
</code></pre>

    <%= render "docs/_h2.html", title: "Types" %>
    <p>You can also create <a href={Routes.website_path(@conn, :doc, ["aml", "types"])}>custom types</a> for better semantics, consistency or re-usability.</p>
    <p>They can be defined inline in the entity attribute definition when not re-used, on standalone for more global usage:</p>
    <pre><code class="hljs aml">type my_type # just a named type for better semantics, not really necessary in AML as types can be anything
type id_type uuid # here is a type alias
type bug_status (draft, "in progress", done) # enums are quite useful and explicit
type position {x int, y int} # even structs can be defined
type float8_range `RANGE (subtype = float8, subtype_diff = float8mi)` # custom types allows any specific definition
</code></pre>

    <%= render "docs/_h2.html", title: "Full example" %>
    <p>
        Now let's write a longer AML example to see how it looks like to design your database schema with AML.
        This example won't use every available feature on AML but give you a good idea of the kind of code you will write using AML.
    </p>
    <p>Let's define a theoretical e-commerce shop:</p>
    <p><img src={Routes.static_path(@conn, "/images/doc/e-commerce-using-aml.png")} alt="e-commerce schema defined using AML"></p>
    <pre><code class="hljs aml">#
# Identity domain
#

users
  id uuid pk
  slug varchar unique | user identifier in urls
  role user_role(customer, staff, admin)
  name varchar
  avatar url
  email varchar unique
  email_validated timestamp nullable
  phone varchar unique
  phone_validated timestamp nullable
  bio text nullable
  company varchar nullable
  locale locale(en, fr)
  created_at timestamp
  updated_at timestamp
  last_login timestamp

credentials
  provider_id provider(google, facebook, twitter, email) pk
  provider_key varchar pk | user id in provider system
  hasher hash_method(md5, sha1, sha256)
  password_hash varchar
  password_salt varchar
  user_id uuid -> users(id)

social_profiles
  user_id uuid -> users(id)
  platform social_platform(facebook, twitter, instagram, slack, github)
  platform_user varchar
  created_at timestamp

#
# Catalog domain
#

categories
  id uuid pk
  slug varchar unique | category identifier in urls
  name varchar
  description text
  tags "varchar[]"
  parent_category uuid -> categories(id)
  created_at timestamp
  updated_at timestamp

products
  id uuid pk
  category_id uuid nullable -> categories(id)
  title varchar
  picture varchar
  summary text
  description text
  price number | in Euro
  discount_type discount_type(none, percent, amount)
  discount_value number
  tags "varchar[]"
  created_at timestamp
  updated_at timestamp

reviews
  id uuid pk
  user_id uuid -> users(id)
  product_id uuid -> products(id)
  rating int index | between 1 and 5
  comment text
  created_at timestamp

#
# Cart domain
#

carts
  id uuid pk
  status cart_status(active, ordered, abandonned)
  created_at timestamp=`now()`
  created_by uuid -> users(id)
  updated_at timestamp

cart_items
  cart_id uuid pk -> carts(id)
  product_id uuid pk -> products(id)
  price number
  quantity int check(`quantity > 0`) | should be > 0
  created_at timestamp

#
# Order domain
#

orders
  id uuid pk
  user_id uuid -> users(id)
  created_at timestamp

order_lines
  id uuid pk
  order_id uuid -> orders(id)
  product_id uuid -> products(id) | used as reference and for re-order by copy data at order time as they should not change
  price number | in Euro
  quantity int check(`quantity > 0`) | should be > 0
</code></pre>
    <p>
        If you want more examples, there is a
        <a href="https://raw.githubusercontent.com/azimuttapp/azimutt/refs/heads/main/demos/ecommerce/source_00_design.md" target="_blank" rel="noopener noreferrer">much longer example</a>
        for the <a href="https://github.com/azimuttapp/azimutt/blob/main/demos/ecommerce/README.md" target="_blank" rel="noopener noreferrer">e-commerce full demo</a>,
        and another one with <a href="https://github.com/azimuttapp/azimutt/blob/main/libs/aml/resources/full.aml" target="_blank" rel="noopener noreferrer">all the AML features</a> ^^.
    </p>
    <p>I hope you enjoyed <a href={Routes.website_path(@conn, :aml)}>AML</a> and can only wish you happy hacking on <a href={Routes.website_path(@conn, :index)}>Azimutt</a>!</p>

    <%= render "docs/_h2.html", title: "Migration from v1" %>
    <p>
        This new version of AML is coming 2 years after the first one
        (<a href={Routes.blog_path(@conn, :show, "aml-a-language-to-define-your-database-schema")}>post</a> & <a href="https://github.com/azimuttapp/azimutt/pull/98">PR</a> ^^).
        During this time we discovered a lot of new use cases and some shortcomings (such as composite foreign keys).
    </p>
    <p>
        This new iteration fixes the issues, improve consistency and add nice features such as <a href={Routes.website_path(@conn, :doc, ["aml", "namespaces"])}>namespace</a>,
        <a href={Routes.website_path(@conn, :doc, ["aml", "properties"])}>properties</a>,
        <a href={"#{Routes.website_path(@conn, :doc, ["aml", "entities"])}#nested-attribute"}>nested attributes</a>,
        <a href={"#{Routes.website_path(@conn, :doc, ["aml", "relations"])}#polymorphic-relation"}>polymorphic relations</a> and more.
    </p>
    <p>
        We made it mostly retro-compatible, so you only have to fix the issued warnings in most cases.
        If you want to look at what needs to be adapted, look at the <a href={Routes.website_path(@conn, :doc, ["aml", "migration"])}>migration doc</a>,
        or just use our <a href={Routes.website_path(@conn, :convert, "amlv1", "aml")}>converter</a> ^^.
    </p>

    <%= render "docs/_h2.html", title: "Other database schema DSLs" %>
    <p>Of course, AML is not the only DSL for database design, here are some alternatives:</p>
    <ul>
        <li><a href={Routes.website_path(@conn, :comparison, "database-design-language", "dbml")} target="_blank" rel="noopener">DBML</a></li>
        <li><a href="https://www.quickdatabasediagrams.com" target="_blank" rel="noopener noreferrer">Quick DBD syntax</a></li>
        <li><a href="https://github.com/oracle/quicksql" target="_blank" rel="noopener noreferrer">Quick SQL</a></li>
        <li><a href="https://docs.eraser.io/docs/syntax-1" target="_blank" rel="noopener noreferrer">Eraser syntax</a></li>
        <li>suggest more if you know others ;)</li>
    </ul>
<% end %>

<%= render "docs/_footer.html", conn: @conn, page: @page, prev: @prev, next: @next %>
